package com.ruicar.afs.cloud.gateway.components;

import com.alibaba.fastjson.JSONObject;
import com.ruicar.afs.cloud.common.core.constant.CommonConstants;
import com.ruicar.afs.cloud.common.core.security.constant.SystemInnerConstants;
import com.ruicar.afs.cloud.common.core.security.feign.Oauth2OperationService;
import com.ruicar.afs.cloud.common.core.util.Base64Util;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Component
@Slf4j
@AllArgsConstructor
public class OpenApiAccessToken {

    private static final String KEY_PREFIX="OPEN-API-ACCESS-TOKEN";
    private final StringRedisTemplate stringRedisTemplate;
    private final Oauth2OperationService oauth2OperationService;
    private static final Lock LOCK = new ReentrantLock();

    public String getAccessToken(String clientId,String clientSecret){
        Assert.isTrue(StringUtils.isNotEmpty(clientId)&&StringUtils.isNotEmpty(clientSecret),"clientId或clientSecret为空");
        log.info("open api 获取 token clientId=[{}]",clientId);
        if(TokenHolder.tokenMap.get(clientId)==null||(System.currentTimeMillis()-TokenHolder.tokenMap.get(clientId).timeStamp)>25*60*1000){
            log.info("未缓存token，或者token过期,获取token");
            LOCK.lock();
            try{
                genToken(clientId,clientSecret);
            }finally {
                LOCK.unlock();
            }
        }
        return TokenHolder.tokenMap.get(clientId).accessToken;
    }

    private void genToken(String clientId,String clientSecret){
        JSONObject redisObject = JSONObject.parseObject(stringRedisTemplate.opsForValue().get(MessageFormat.format("{0}-{1}",KEY_PREFIX,clientId)),JSONObject.class);
        if(redisObject==null){
            login(clientId,clientSecret);
        }else{
            if((System.currentTimeMillis()-redisObject.getLong(CommonConstants.UPD_TIME))>25*60*1000){
                login(clientId,clientSecret);
            }else{
                TokenHolder.tokenMap.put(clientId,new Token(redisObject.getString(CommonConstants.ACCESS_TOKEN),redisObject.getLong(CommonConstants.UPD_TIME)));
            }
        }
    }


    private void login(String clientId,String clientSecret){
        Map<String, String> requestParam = new HashMap<>(8);
        requestParam.put(CommonConstants.GRANT_TYPE, "password");
        requestParam.put(CommonConstants.USERNAME, SystemInnerConstants.SYSTEM_ADMIN);
        requestParam.put(CommonConstants.PASSWORD, CommonConstants.DEFAULT_PASS);
        Map<String, String> requestHeader = new HashMap<>(8);
        requestHeader.put(CommonConstants.AUTHORIZATION, "Basic " + Base64Util.encode((clientId+ ":" + clientSecret).getBytes()));

        JSONObject loginObject = JSONObject.parseObject(JSONObject.toJSONBytes(oauth2OperationService.login(requestParam,requestHeader)),JSONObject.class);
        if(loginObject.containsKey("access_token")) {
            JSONObject redisObject = new JSONObject();
            redisObject.put(CommonConstants.ACCESS_TOKEN, loginObject.get("access_token"));
            redisObject.put(CommonConstants.UPD_TIME, System.currentTimeMillis());
            TokenHolder.tokenMap.put(clientId,new Token(redisObject.getString(CommonConstants.ACCESS_TOKEN),redisObject.getLong(CommonConstants.UPD_TIME)));

            stringRedisTemplate.opsForValue().set(
                    MessageFormat.format("{0}-{1}",KEY_PREFIX,clientId)
                    ,redisObject.toJSONString()
                    ,loginObject.getLong("expires_in")
                    ,TimeUnit.SECONDS);
        }else{
            log.error("open api 获取token 失败{}",loginObject.containsKey("msg")?loginObject.getString("msg"):"");
            if(loginObject.containsKey("code")&&loginObject.getString("code").equals("403")){
                throw new RuntimeException("clientId或clientSecret错误");
            }

            throw new RuntimeException("服务访问失败");
        }
    }

    static class TokenHolder{
        private TokenHolder() {}
        static final Map<String,Token> tokenMap= new ConcurrentHashMap(8);

    }

    static class Token{
        Token(String accessToken,Long timeStamp){
            this.accessToken = accessToken;
            this.timeStamp = timeStamp;
        }
        private String accessToken;
        private Long timeStamp=1L;
    }
}
